/*
 * ===================================================================
 *  
 *      Filename:  log_client_loadbalance.c
 *
 *   Description:  This file is the main routine for the loadbalance.
 *                 The format of the policy file could be seen in the 
 *                 TLD(top level design),we support the exact and wildcast
 *                 modes.but the wildcast has the lowest priority.In the
 *                 exact section, the first one has the lowest priority.
 *
 *       Version:  1.0
 *        Create:  2010/07/13
 *
 *        Author:  Keven Zhu     kevenzhu@tencent.com
 *     Copyright:  Reserved by Tencent.com Co.ltd
 *          
 * ====================================================================
 */
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <linux/types.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <net/if_arp.h>

#include "lib/list.h"
#include "lib/util.h"
#include "log_client_api.h"

#define __USE_GNU
#include <string.h>

struct list_head global_lbnode_list;
unsigned int    destination_srv_address = 0;
unsigned int    local_nic_addr = 0;
pthread_mutex_t lbnode_mutex;

/*define the struct for the destination routine*/
typedef struct lb_node_
{
    struct list_head list;
    unsigned int    min_addr;
    unsigned int    max_addr;
    unsigned int    destination;
}lb_node;

/*add node to the list, as the name :)*/
int add_node_to_list(lb_node *node)
{
    pthread_mutex_lock(&lbnode_mutex);
    list_add(&node->list,&global_lbnode_list);
    pthread_mutex_unlock(&lbnode_mutex);
    return NOERR;
}

int test_lbnode()
{
    lb_node *tmp_node,*n_node;
    pthread_mutex_lock(&lbnode_mutex);
    list_for_each_entry_safe(tmp_node,n_node,&global_lbnode_list,list)
    {
        log_doctor(doctor_level,"mmin %02x max %02x des %02x \n",tmp_node->min_addr,tmp_node->max_addr,tmp_node->destination);
    }
    pthread_mutex_unlock(&lbnode_mutex);
    return NOERR;
}

/*free all the nodes in the list,it will be called after the update or the addr is unreachable*/
int free_lbnode()
{
    lb_node *tmp_node,*n_node;
    pthread_mutex_lock(&lbnode_mutex);
    list_for_each_entry_safe(tmp_node,n_node,&global_lbnode_list,list)
    {
        list_del(&tmp_node->list);
        free(tmp_node);
    }
    pthread_mutex_unlock(&lbnode_mutex);
    return NOERR;
}

/*return the srv address to the daemon, maybe it's invalidate*/
unsigned int get_destination()
{
    return destination_srv_address;
}

/* return the max and min of the src address
 * BE care!!!
 * the returned value is based on the host order, not the networking order
 * @char *str: the src address 
 * @mask: the mask of the src address
 * */
int convert_addr_to_range(const char *str,unsigned int mask,unsigned int* min,unsigned int* max)
{
    unsigned int l;
    unsigned int val;
    unsigned int local_mask,i;

    l = 0;
    for (i = 0; i < 4; i++)
    {
        l <<= 8;
        if (*str != '\0' && *str != '/')
        {
            val = 0;
            while (*str != '\0' && *str != '.' && *str != '\n' && *str != '/')
            {
                if( *str == '\t' || *str == 0x20)
                    str++;
                else
                {
                    val *= 10;
                    val += *str - '0';
                    str++;
                }
            }
            l |= val;
            if (*str != '\0' && *str != '/' ) 
                str++;
        }
    }
    if(mask)
    {
        /*now we get the address value*/
        i = (1<<(32-mask))-1; /*get the max range of the address*/
        local_mask = ~i;  /*get the mask for the address*/
    }
    else
    {
        /*the default routine 0.0.0.0/0.0.0.0*/
        i = 0xffffffff;
        local_mask = 0;
    }

    *min = l & local_mask;
    *max = *min + i;
    return NOERR;
}

/* return the max and min of the src address
 * BE care!!!
 * the returned value is based on the host order, not the netwoking order
 * @char *str: the src address 
 * @mask: the mask of the src address
 * */
unsigned int convert_addr_to_int(const char *str)
{
    unsigned int l;
    unsigned int val;
    unsigned int i;

    l = 0;
    for (i = 0; i < 4; i++)
    {
        l <<= 8;
        if (*str != '\0' && *str != '/')
        {
            val = 0;
            while (*str != '\0' && *str != '.' && *str != '\n' && *str != '/')
            {
                if( *str == '\t' || *str == 0x20)
                    str++;
                else
                {
                    val *= 10;
                    val += *str - '0';
                    str++;
                }
            }
            l |= val;
            if (*str != '\0' && *str != '/' ) 
                str++;
        }
    }
    return l;
}

int parse_policy_file_and_reorganize_list()
{
    #define MAX_LINE_COUNTER 56
    #define DEFAULT_GW       1
    #define EXACT_GW         2  
    char buff[MAX_LINE_COUNTER];
    char *tmp_buff,*mask_buff;
    FILE *policy_file;
    int  section_flag;/*0-version   1-global   2-server*/
    lb_node *lbnode;
    unsigned int  max,min,mask;
    policy_file = fopen(POLICY_FILE_PATH,"r");
    if(!policy_file)
        return INVIO;
    /* below is the format, 
     * "*" meas wildcasr
     * the address must cooperate with the mask
    [version]
    version=1
    [global]
    *    10.1.136.8
    *    10.1.136.7
    *    10.1.136.6
    [server]
    10.3.135.202/32    10.1.136.8
    1.1.1.1/24         10.1.136.9
    */
    section_flag = 0;
    while(fgets(buff,MAX_LINE_COUNTER,policy_file))
    {
        /*we reader the content from the file one by one*/
        if(strcasestr(buff,"global"))
        {
            section_flag = DEFAULT_GW;
            continue;
        }
        if(strcasestr(buff,"server"))
        {
            section_flag = EXACT_GW;
            continue;
        }
        if(strcasestr(buff,"version"))
        {
            section_flag = 0;
            continue;
        }
        /*we must create the lbnode*/
        lbnode = (lb_node*)malloc(sizeof(lb_node));
        if(!lbnode)
        {
            log_doctor(doctor_level,"no memory for the lb_node\n");
            continue;
        }
        /*add the node to the list, main logic*/
        if(section_flag == DEFAULT_GW)
        {
            /*get the default gw*/
            tmp_buff = buff;
            while(*tmp_buff)
            {
                if((*tmp_buff >='0') && (*tmp_buff <='9'))
                    goto CP_DF_GW;
                else
                    tmp_buff++;
            }
            goto NOSENCE_LINE;
CP_DF_GW:            
            /*we want to get the server address*/
            lbnode->destination = convert_addr_to_int(tmp_buff);
            /*this is the default node,we deal with it with some strick*/
            lbnode->min_addr    = 0;
            lbnode->max_addr    = 0;
        }
        else if(section_flag == EXACT_GW)
        {
            /*get the default gw*/
            tmp_buff = buff;
            while(*tmp_buff)
            {
                if((*tmp_buff >='0') && (*tmp_buff <='9'))
                    goto CP_EX_GW;
                else
                    tmp_buff++;
            }
            goto NOSENCE_LINE;
CP_EX_GW:            
            mask_buff = tmp_buff;
            /*search the '/' symbol*/
            while(*mask_buff)
            {
                if(*mask_buff == '/')
                    break;
                else
                    mask_buff++;
            }
            /*skip the '/' symbol*/
            mask_buff++;
            /*first we get the mask size*/
            mask = atoi(mask_buff);
            /*then we get the max and min*/
            convert_addr_to_range(tmp_buff,mask,&min,&max);
            lbnode->min_addr = min;
            lbnode->max_addr = max;
             /*get the default gw*/
            while(*mask_buff)
            {
                if((*mask_buff >='0') && (*mask_buff <='9'))
                    mask_buff++;
                else
                    break;
            }
            /*in the end,we have the gw*/
            lbnode->destination = convert_addr_to_int(mask_buff); 
            /*soooooooooooooooooooo boring!!!!*/
        }
        else
        {
            lbnode->min_addr    = 0xffffffff;
            lbnode->max_addr    = 0xffffffff;
            lbnode->destination = 0;
            log_doctor(doctor_level,"complaining, why I goto here\n");
        }
        add_node_to_list(lbnode);
        continue;
NOSENCE_LINE:
        free(lbnode);
    }
    fclose(policy_file);
    return NOERR;
}

/*the */
int remove_srv_from_list(unsigned int server)
{
    lb_node *tmp_node,*n_node;
    pthread_mutex_lock(&lbnode_mutex);
    list_for_each_entry_safe(tmp_node,n_node,&global_lbnode_list,list)
    {        
        /*the flags for the default gw*/
        if(!(tmp_node->max_addr) && !(tmp_node->min_addr) && tmp_node->destination==server)
        {  
            log_doctor(doctor_level,"remove default,gw is %02x\n",tmp_node->destination);
            /*this is the global srv list,each time,we will push it to the tail*/
            list_del(&tmp_node->list);
            free(tmp_node);
        }
        else
        {
            if((local_nic_addr >= tmp_node->min_addr) && (local_nic_addr <= tmp_node->max_addr) && tmp_node->destination==server)
            {
                log_doctor(doctor_level,"remove serv node max%02x min%02x \n",tmp_node->max_addr,tmp_node->min_addr);
                /*we are in the range, return the destination*/
                /*this is the global srv list,each time,we will push it to the tail*/
                list_del(&tmp_node->list);
                free(tmp_node);
            }
        }
    }
    pthread_mutex_unlock(&lbnode_mutex);
    /*Not found the srv, 0 will be returned*/
    return NOERR;

}
unsigned int search_srv_from_list(unsigned int client, int list_type)
{    
    lb_node *tmp_node,*n_node;
    pthread_mutex_lock(&lbnode_mutex);
    list_for_each_entry_safe(tmp_node,n_node,&global_lbnode_list,list)
    {
        /*the flags for the default gw*/
        if(!(tmp_node->max_addr) && !(tmp_node->min_addr) )
        {  
            log_doctor(doctor_level,"this is the default,gw is %02x\n",tmp_node->destination);
            /*this is the global srv list,each time,we will push it to the tail*/
            list_del(&tmp_node->list);
            list_add_tail(&tmp_node->list,&global_lbnode_list);
            pthread_mutex_unlock(&lbnode_mutex);
            return tmp_node->destination;
        }
        else
        {
            if(list_type == SEARCH_ALL)
            {
                log_doctor(doctor_level,"serv node max%02x min%02x \n",tmp_node->max_addr,tmp_node->min_addr);
                if((client >= tmp_node->min_addr) && (client <= tmp_node->max_addr))
                {
                    /*we are in the range, return the destination*/
                    pthread_mutex_unlock(&lbnode_mutex);
                    return tmp_node->destination;
                }
            }
        }
    }
    pthread_mutex_unlock(&lbnode_mutex);
    /*Not found the srv, 0 will be returned*/
    return 0;
}

extern unsigned int get_nic_list(struct ifreq*);
/* as the name*/
int find_fittest_addr(int list_type)
{
    #define MAXINTERFACES   16
    struct ifreq nic_buf[MAXINTERFACES];

    int nic_counter   = 0;
    int final_srv_add = 0;
    unsigned int tmp_addr   = 0;
    unsigned int tmp_srv_addr = 0;
    nic_counter = get_nic_list ( nic_buf );

    /*I don't which nic will be used,but all the nic will be checked*/
    while(nic_counter)
    {
        nic_counter --;
        if(nic_buf[nic_counter].ifr_flags )
        {
            tmp_addr = ((struct sockaddr_in*)(&nic_buf[nic_counter].ifr_addr))->sin_addr.s_addr;
            if(tmp_addr != 16777343)/*127.0.0.1*/
            {
                /*we must change it to network order*/
                log_doctor(doctor_level,"search %02x for gw\n",htonl(tmp_addr));
                tmp_srv_addr = search_srv_from_list(htonl(tmp_addr),list_type);
                if(tmp_addr)
                {
                    local_nic_addr = tmp_addr;
                    final_srv_add  = tmp_srv_addr;
                }
                else
                    continue;
            }
            else
                continue;
        }
        else
        continue;
    }
    return final_srv_add;
}

/*update the destination for the log srv, the address for the get_destination_addr will be updated*/
int update_destination_addr(int list_type)
{
    int iret = 0;
    /*if we update the default gw,we just adjust the sequence of the default gw*/
    if(list_type == SEARCH_ALL)
    {
        /*free the list first*/
        free_lbnode(); 
        /*construct the list again*/
        iret = parse_policy_file_and_reorganize_list();
        if(iret)
        {
            destination_srv_address = 0;
            return iret;
        }
    }
    /*find the fittest node*/
    destination_srv_address = find_fittest_addr(list_type);
    log_doctor(doctor_level,"srv is %02x\n",destination_srv_address);
    if(!destination_srv_address)
    {
        /*flush the local backup file*/
        flush_backup_to_srv_with_tcp();        
        return INVIO;
    }
    else
    {
        return NOERR;
    }
}

int init_loadbalance()
{
    INIT_LIST_HEAD(&global_lbnode_list);
    pthread_mutex_init(&lbnode_mutex,NULL);
    return NOERR;
}

int finit_loadbalance()
{
    /*destroy the list*/
    free_lbnode();
    /*destroy the mutex*/
    pthread_mutex_destroy(&lbnode_mutex);
    return NOERR;
}


